package com.ebarlas.fractal.compile;

import java.util.List;

import org.apache.bcel.Constants;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;

public class CompilerImpl implements Compiler {
    
    private static int compileCount = 0;
    
    private ClassGen createClass(Class<? extends CompiledExpression> parent) {
        // create unique class name
        String className = "CompiledExpr_" + (++compileCount);        
        
        ClassGen cg = new ClassGen(
                className, // class name
                parent.getName(), // parent
                "<generated>", // file name
                Constants.ACC_PUBLIC | Constants.ACC_SUPER, // access mods
                null ); // interfaces  
        
        return cg;
    }

    @SuppressWarnings("unchecked")
    public <T extends CompiledExpression> T compile(Class<T> parent, List<StackCommand> commands) {
        ClassGen cg = createClass(parent);
        
        // create constructor
        createConstructor(cg, commands);
        
        // create execute method
        createExecute(cg, commands);   
        
        try {
            byte[] classBytes = cg.getJavaClass().getBytes();
            ByteClassLoader.registerClass(cg.getClassName(), classBytes);
            ByteClassLoader loader = ByteClassLoader.getInstance();                      
            Class<T> clazz = (Class<T>)loader.loadClass(cg.getClassName());
            T t = clazz.newInstance();
            ByteClassLoader.unregisterClass(cg.getClassName());
            return t;
        } catch(ClassNotFoundException e) { 
            return null;
        } catch(InstantiationException e) { 
            return null;
        } catch(IllegalAccessException e) { 
            return null;
        }        
    }
    
    private void createConstructor(ClassGen cg, List<StackCommand> commands) {
        ConstantPoolGen cp = cg.getConstantPool(); 
        InstructionList il = new InstructionList();
        InstructionFactory factory = new InstructionFactory(cg);   
        
        // create constructor
        MethodGen mg = new MethodGen(
                Constants.ACC_PUBLIC, // access flags
                Type.VOID, // return type
                new Type[] {}, // arg types
                new String[] {}, // arg names
                "<init>", // method 
                cg.getClassName(), // class
                il, // inst list
                cp); // const pool gen
        
        // invoke parent constructor
        il.append(InstructionFactory.createThis());
        il.append(factory.createInvoke(
                cg.getSuperclassName(), 
                "<init>", 
                Type.VOID, 
                new Type[] {}, 
                Constants.INVOKESPECIAL));  
        
        // compile constructor for stack commands
        for(StackCommand command : commands) {
            command.compileConstructor(cp, il, factory);
        }     
        
        // return
        il.append(InstructionConstants.RETURN);       

        // cleanup
        mg.setMaxStack();
        cg.addMethod(mg.getMethod());
        il.dispose();        
    }
    
    private void createExecute(ClassGen cg, List<StackCommand> commands) {
        ConstantPoolGen cp = cg.getConstantPool();        
        InstructionList il = new InstructionList();
        InstructionFactory factory = new InstructionFactory(cg); 
        
        // create execute method
        MethodGen mg = new MethodGen(
                Constants.ACC_PUBLIC, // access flags
                CompilerUtils.COMPLEX_TYPE, // return type
                new Type[] {}, // arg types
                new String[] {}, // arg names
                "execute", // method 
                cg.getClassName(), // class
                il, // inst list
                cp); // const pool gen  
        
        // compile execute for stack commands
        for(StackCommand command : commands) {
            command.compileExecute(cp, il, factory);         
        }  
        
        // return complex reference
        il.append(InstructionFactory.createReturn(CompilerUtils.COMPLEX_TYPE));           
        
        // cleanup
        mg.setMaxStack();
        cg.addMethod(mg.getMethod());
        il.dispose();         
    }
    
}

